import os
import time

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QLabel, QTreeWidgetItem, QAction, QMenu, QMessageBox, QTreeWidget, QPlainTextEdit, \
    QScrollBar, QPushButton, QDoubleSpinBox, QCheckBox, QComboBox, QFileDialog, QTableWidget, QTableWidgetItem
from PyQt5.uic import loadUi

from base_api.base import PygameMixerTaskClass
from base_api.base.MessageClass import Message
from base_api.base.PygameMixerTaskClass import PygameMixerTask
from base_api.base.VideoInfoClass import VideoInfo
from pyqt_api import TreeWidgetModule
from pyqt_api.PyqtThreadPoolClass import PyqtThreadPool

from pydub import AudioSegment

"""
音频处理子窗口
"""


class MakeWindow:
    def __init__(self, home_window):
        self.window = loadUi('ui/make.ui')
        self.home_window = home_window

        # 两个专用文件，分别用于当前音频处理和存储音轨拼接
        self.path_1 = self.home_window.this_workspace_dir + '/temp2/this.wav'
        self.path_2 = self.home_window.this_workspace_dir + '/temp2/total.wav'

        # 待播放音频文件的路径
        self.this_video_path = ''
        # 待播放音频文件的信息
        self.this_video_info = None
        """
        两个时间节点，float类型，单位秒
        1表示当前音频的开始节点，2表示当前音频的实时播放节点。
        2有三种场景
        (1)如果在播放过程中，2由1和播放器实时反馈的播放时长决定，进而设置进度条的显示。
        (2)如果在播放停止时，拖拽进度条，会根据判断是人为操作影响，对2基于进度条进行赋值。
        (3)点击树组件或者上一首、下一首，切换音频时，1和2都置为0
        在决定播放音频时，会根据2设置1
        """
        self.this_video_point_1 = 0.0
        self.this_video_point_2 = 0.0

        """树组件"""
        self.window.treeWidget.header().hide()
        self.window.treeWidget.setColumnCount(1)
        # 树组件刷新显示
        self.treeWidget_refresh()
        # 右键菜单
        self.window.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.window.treeWidget.customContextMenuRequested.connect(self.treeWidget_rightmenu)
        # 双击节点 如果是文件则进行选中和播放
        self.window.treeWidget.itemDoubleClicked.connect(self.treeWidget_itemDoubleClicked)

        # 进度条组件
        self.window.horizontalScrollBar: QScrollBar
        self.window.horizontalScrollBar.setMinimum(0)
        # 此设置是无效的、会被覆盖，该子窗口中进度条的移动单位对应1毫秒，最大单位数对应当前音频长度
        # self.window.horizontalScrollBar.setMaximum(100)
        self.window.horizontalScrollBar.setValue(0.0)
        self.window.horizontalScrollBar.valueChanged.connect(self.horizontalScrollBar_valueChanged)
        # 默认选中
        self.window.checkBox: QCheckBox
        self.window.checkBox.setChecked(True)
        self.window.checkBox.clicked.connect(self.checkBox_clicked)

        # 播放音频任务对象
        self.pygameMixerTask = PygameMixerTask()
        # 线程对象
        self.pyqtThreadPool = None
        # 工作状态，0表示未开始/结束，1表示运行中
        self.work_status = 0

        # 播放/暂停 按钮
        self.window.pushButton: QPushButton
        self.window.pushButton.clicked.connect(self.control)
        # 保存按钮
        self.window.pushButton_2: QPushButton
        self.window.pushButton_2.clicked.connect(self.save)
        # 另存为
        self.window.pushButton_3: QPushButton
        self.window.pushButton_3.clicked.connect(self.save_another)
        # 分割
        self.window.pushButton_4: QPushButton
        self.window.pushButton_4.clicked.connect(self.split)
        # 间隔分割
        self.window.pushButton_5: QPushButton
        self.window.pushButton_5.clicked.connect(self.split_per)
        # 选中全部
        self.window.pushButton_6: QPushButton
        self.window.pushButton_6.clicked.connect(self.choose_all)
        # 选中单个
        self.window.pushButton_7: QPushButton
        self.window.pushButton_7.clicked.connect(self.choose_one)
        # 添加到音轨中
        self.window.pushButton_8: QPushButton
        self.window.pushButton_8.clicked.connect(self.pushButton_8_clicked)
        """表格组件"""
        self.window.tableWidget: QTableWidget
        self.window.tableWidget.setRowCount(1)
        self.window.tableWidget.setColumnCount(0)
        self.window.tableWidget.verticalHeader().hide()
        self.window.tableWidget.horizontalHeader().hide()
        self.window.tableWidget.horizontalHeader().setStretchLastSection(True)
        # 右键菜单
        self.window.tableWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.window.tableWidget.customContextMenuRequested.connect(self.tableWidget_rightMenu)
        # 点击单元格
        self.window.tableWidget.itemClicked.connect(self.tableWidget_itemClicked)
        # 列索引，用于帮助添加或分割时输出表格组件中
        self.current_column = 0

    def checkBox_clicked(self):
        self.window.checkBox: QCheckBox
        if self.work_status == 0:
            if self.window.checkBox.isChecked():
                self.window.doubleSpinBox_2: QDoubleSpinBox
                self.window.label: QLabel
                self.window.doubleSpinBox_2.setValue(float(self.window.label.text()))

    def save(self):
        # 检查
        if self.this_video_path == '':
            QMessageBox.warning(self.window, '警告', '未选中任何音频文件')
        elif not os.path.exists(self.this_video_path):
            QMessageBox.warning(self.window, '警告', '选中音频文件已被删除')
        elif self.this_video_path in [self.path_1, self.path_2]:
            QMessageBox.warning(self.window, '警告', '当前选中文件为固定缓存文件，建议另存')
        else:
            # 刷新当前文件的处理缓存文件
            self.create_now_audio()
            # 读取
            temp_audio = AudioSegment.from_file(self.path_1, format='wav')
            # 写入
            self.this_video_info: VideoInfo
            temp_audio.export(self.this_video_path, format=self.this_video_info.filename_suffix[1:])
            QMessageBox.information(self.window, '提示', '保存完成')

    def save_another(self):
        result = QFileDialog.getSaveFileName(self.window, '保存音频文件', '')
        if result[0] != '':
            if self.this_video_path == '' or not os.path.exists(self.this_video_path):
                QMessageBox.warning(self.window, '警告', '未选中任何音频文件或者已被删除')
                return
            self.create_now_audio()
            # 输出
            audio = AudioSegment.from_file(self.path_1, format='wav')
            audio.export(result[0])
            # 输出完毕
            QMessageBox.information(self.window, '提示', '保存完毕')

    def split(self):
        # 读取时间节点
        value = self.this_video_point_2
        # 判断
        if value == 0:
            QMessageBox.warning(self.window, '警告', '请移动时间节点，不能为0或末尾')
            return
        temp_audio = AudioSegment.from_file(self.this_video_path)
        # 分割
        split_list = list()
        split_list.append(temp_audio[:int(value * 1000)])
        split_list.append(temp_audio[int(value * 1000):])

        # 遍历保存和输出表格
        path_list = list()
        for i in range(len(split_list)):
            # 默认保存名为原文件名+顺序+时间戳
            filename = '%s_%s_%s.wav' % (self.this_video_info.filename_pre, str(i + 1), time.strftime('%d%H%M%S'))
            filePath = self.home_window.this_workspace_dir + '/temp2/' + filename
            split_list[i].export(filePath, format='wav')
            path_list.append(filePath)
        self.tableWidget_insertItems(path_list)

    def split_per(self):
        # 读取数值，按多少秒分割
        self.window.doubleSpinBox_6: QDoubleSpinBox
        per = self.window.doubleSpinBox_6.value()
        # 检查
        if per == 0:
            QMessageBox.warning(self.window, '警告', '必须设置间隔时间段')
            return

        temp_audio = AudioSegment.from_file(self.this_video_path)
        split_list = list()
        # 循环分割
        i = 0
        while True:
            temp_video_n = temp_audio[i * per * 1000: (i + 1) * per * 1000]
            split_list.append(temp_video_n)
            i = i + 1
            if i * 1000 >= self.this_video_info.length_sec:
                break
        # 遍历保存和输出表格
        path_list = list()
        for i in range(len(split_list)):
            filename = '%s_%s_%s.wav' % (self.this_video_info.filename_pre, str(i + 1), time.strftime('%d%H%M%S'))
            filePath = self.home_window.this_workspace_dir + '/temp2/' + filename
            split_list[i].export(filePath, format='wav')
            path_list.append(filePath)
        self.tableWidget_insertItems(path_list=path_list)

    def choose_all(self):
        self.window.tableWidget: QTableWidget
        # 遍历表格，获取路径队列
        path_list = list()
        for i in range(self.window.tableWidget.columnCount()):
            path = self.window.tableWidget.item(0, i).text()
            if path == '' or not os.path.exists(path) or not os.path.isfile(path):
                QMessageBox.warning(self.window, '警告', '全选操作必须确保各单元格存在有效的音频文件路径，检查时第%s列发生不符合' %
                                    str(i + 1))
                return
            path_list.append(path)
        if len(path_list) == 0:
            QMessageBox.warning(self.window, '警告', '没有任何音频文件')
            return
        # 合并各音频
        temp_audio = AudioSegment.from_file(path_list[0])
        for i in range(1, len(path_list)):
            temp_audio = temp_audio + AudioSegment.from_file(path_list[i])
        # 缓存合并音频
        temp_audio.export(self.path_2, format='wav')
        # 将合并音频作为待操作音频
        self.set_this_video(self.path_2)

    def choose_one(self):
        self.window.tableWidget: QTableWidget
        # 当前选中索引
        column_index = self.window.tableWidget.currentColumn()
        if column_index != -1:
            # 当前选中路径
            path = self.window.tableWidget.item(0, column_index).text()
            # 检查
            if path == '' or not os.path.exists(path) or not os.path.isfile(path):
                QMessageBox.warning(self.window, '警告', '单选操作必须确保其单元格存在有效的音频文件路径，检查时选中列发生不符合')
                return
            # ok
            self.set_this_video(path)

    """
    根据当前音频文件，结合相关设置，生成播放用音频文件
    """

    def create_now_audio(self):
        self.window.doubleSpinBox_2: QDoubleSpinBox
        start_time = self.window.doubleSpinBox_2.value()
        self.this_start_point = start_time
        self.window.doubleSpinBox_3: QDoubleSpinBox
        # 如果为0，表示直到末尾
        end_time = self.window.doubleSpinBox_3.value()
        self.window.checkBox_2: QCheckBox
        reverse_flag = self.window.checkBox_2.isChecked()
        self.window.doubleSpinBox_4: QDoubleSpinBox
        fade_in = self.window.doubleSpinBox_4.value()
        self.window.doubleSpinBox_5: QDoubleSpinBox
        fade_out = self.window.doubleSpinBox_5.value()

        temp_audio = AudioSegment.from_file(self.this_video_path)
        if start_time != 0:
            temp_audio = temp_audio[start_time * 1000:]
        if end_time != 0:
            temp_audio = temp_audio[:temp_audio * 1000]
        if reverse_flag:
            temp_audio = temp_audio.reverse()
        if fade_in != 0:
            temp_audio = temp_audio.fade_in(fade_in * 1000)
        if fade_out != 0:
            temp_audio = temp_audio.fade_out(fade_out * 1000)

        # 更新输出
        temp_audio.export(self.home_window.this_workspace_dir + '/temp2/this.wav', format='wav')

    def treeWidget_refresh(self):
        self.window.treeWidget: QTreeWidget
        # 清除
        self.window.treeWidget.clear()
        # 以树组件为父节点，添加当前工作目录下的所有目录，以及.mp3、.wav等音频文件。保留空目录
        TreeWidgetModule.treeWidget_addFile(treeWidget=self.window.treeWidget,
                                            dir_path=self.home_window.this_workspace_dir,
                                            file_endWith_list=['.mp3', '.wav'])
        # 以树组件为父节点，添加com公共目录作为顶级节点，加载其下的子目录和音频文件
        com = QTreeWidgetItem(['com'])
        self.window.treeWidget.addTopLevelItem(com)
        TreeWidgetModule.treeWidgetItem_addFile(treeWidgetItem=com, dir_path='data/com',
                                                file_endWith_list=['.mp3', '.wav'])

    def treeWidget_rightmenu(self, pos):
        """ 查看当前选中项，如果是文件则进行文件操作，反之则进行目录相关操作"""
        self.window.treeWidget: QTreeWidget
        # 获取当前节点
        current_item = self.window.treeWidget.currentItem()
        if current_item is not None:
            # 获取其节点和父节点
            item_list = TreeWidgetModule.treeWidgetItem_where(treeWidgetItem=current_item)
            # 根据节点进行拼接, 获取节点路径
            path = TreeWidgetModule.treeWidgetItem_list_toPath(item_list)
            # 如果顶级节点是com
            if item_list[0].text(0) == 'com':
                path = 'data/' + path
            # 否则其顶级节点是在当前工作目录下
            else:
                path = self.home_window.this_workspace_dir + '/' + path
            # 判断节点是否存在
            if not os.path.exists(path):
                QMessageBox.warning(self.window, '警告', '选中节点的路径不存在，需要检查或重新加载')
            else:
                menu = QMenu(self.window.treeWidget)
                menu.addAction('刷新')
                menu.triggered.connect(self.treeWidget_rightmenu_triggered)
                # 将菜单显示在鼠标位置
                menu.exec_(QCursor.pos())

    def treeWidget_rightmenu_triggered(self, act: QAction):
        if act.text() == '刷新':
            self.treeWidget_refresh()

    def treeWidget_itemDoubleClicked(self, item: QTreeWidgetItem, column: int):
        # 获取其节点和父节点
        item_list = TreeWidgetModule.treeWidgetItem_where(treeWidgetItem=item)
        # 根据节点进行拼接, 获取节点路径
        path = TreeWidgetModule.treeWidgetItem_list_toPath(item_list)
        # 如果顶级节点是com
        if item_list[0].text(0) == 'com':
            path = 'data/' + path
        # 否则其顶级节点是在当前工作目录下
        else:
            path = self.home_window.this_workspace_dir + '/' + path
        # 判断节点是否为文件
        if os.path.exists(path) and os.path.isfile(path):
            self.set_this_video(path)

    def horizontalScrollBar_valueChanged(self, value):
        # 未执行状态, 则进度条变化是人为拖拽或者初始归零设置
        if self.work_status == 0 and self.this_video_path!='':
            if value == 0:
                self.this_video_point_2 = 0.0
            else:
                # 基于value计算时间，单位秒
                self.window.horizontalScrollBar: QScrollBar
                self.this_video_point_2 = value/self.window.horizontalScrollBar.maximum()*self.this_video_info.length_sec
                # 保留两位小数
                self.this_video_point_2 = int(self.this_video_point_2*100)/100
            # 同步拖拽或归零操作到标签，显示单位为秒
            self.window.label: QLabel
            self.window.label.setText(str(self.this_video_point_2))

    def set_this_video(self, path: str, flag=True):
        self.this_video_path = path
        # 解析信息
        self.this_video_info = VideoInfo(self.this_video_path)
        # 输出音频信息
        self.window.plainTextEdit: QPlainTextEdit
        self.window.plainTextEdit.setPlainText(self.this_video_info.toString())
        # 归位0
        self.window.horizontalScrollBar: QScrollBar
        self.window.horizontalScrollBar.setValue(0)
        # 设置进度条的移动单位对应1毫秒，整体长度对应播放音频的毫秒数
        self.window.horizontalScrollBar.setMaximum(int(self.this_video_info.length_sec*1000))

    def control(self):
        # 未开始
        if self.work_status == 0:
            # 如果没有选中项，警告
            if self.this_video_path == '' or not os.path.exists(self.this_video_path):
                QMessageBox.warning(self.window, '警告', '未选中任何音频或者选中音频不存在')
                return
            # 当前有其它子窗口的播放器对象在工作
            if not PygameMixerTaskClass.get_chance():
                QMessageBox.warning(self.window, '警告', '当前有其它音频正在播放，请手动关闭或等待')
                return
                # 待变更为直接关闭其它子窗口的工作，然后执行当前子窗口的播放任务
            """拿到音频播放标志"""
            # 线程对象创建
            self.pyqtThreadPool = PyqtThreadPool()
            # 线程对象的消息位初始化
            self.pyqtThreadPool.get_signal()
            if self.pyqtThreadPool.signal_this is None:
                QMessageBox.warning(self.window, '警告', '线程池繁忙，请稍后尝试或者手动关闭某线程工作')
                # 用不上就释放
                PygameMixerTaskClass.set_isBusy_flag(False)
            else:
                # 进行相关检查和赋值
                flag = self.init_pygameMixerTask()
                if flag:
                    # 切换到工作状态
                    self.work_status = 1
                    # 变更按钮文本
                    self.window.pushButton: QPushButton
                    self.window.pushButton.setText('播放中(点击暂停)')
                    # 任务对象的执行方法赋值给线程对象的属性
                    self.pyqtThreadPool.run_task = self.pygameMixerTask.exploit
                    # 绑定消息位
                    self.pyqtThreadPool.signal_this.connect(self.output)
                    # 绑定消息位
                    self.pygameMixerTask.output_access = self.pyqtThreadPool.output
                    # 开启线程
                    self.pyqtThreadPool.start()
                else:
                    # 用不上就释放
                    PygameMixerTaskClass.set_isBusy_flag(False)
        # 正在运行中
        else:
            self.work_status = 0
            self.pygameMixerTask.stop()
            self.window.pushButton.setText('播放')

    def output(self, msg: Message):
        print(msg.status, msg.data)
        # 任务执行过程中出现异常
        if msg.status == -3:
            self.work_status = 0
            QMessageBox.warning(self.window, '警告', f'任务执行过程中出现异常，已结束。{msg.data}')
            PygameMixerTaskClass.set_isBusy_flag(False)
        # 消息类结束执行。在测试功能中，不会再开子线程，意味着其表示本次任务的完成
        elif msg.status == -1:
            self.work_status = 0
            PygameMixerTaskClass.set_isBusy_flag(False)
            self.window.pushButton: QPushButton
            self.window.pushButton.setText('播放')

        # 音频播放进度反馈
        elif msg.status == 1:
            self.window.horizontalScrollBar: QScrollBar
            self.window.label: QLabel
            # 为-1表示音频播放完成，直接赋值为音频的最大长度
            if msg.data == -1:
                self.this_video_point_2 = int(self.this_video_info.length_sec*100)/100
                temp = self.window.horizontalScrollBar.maximum()
            # 中间过程，传递的是播放时长，单位毫秒
            else:
                # 计算音频当前播放节点
                self.this_video_point_2 = self.this_video_point_1 + msg.data/1000
                temp = int(self.this_video_point_2/self.this_video_info.length_sec *
                           self.window.horizontalScrollBar.maximum())
            # 标签标记进度，单位秒
            self.window.label.setText(str(int(self.this_video_point_2*100)/100))
            # 进度条设置
            self.window.horizontalScrollBar.setValue(temp)
            # 如果勾选了同步多选框
            self.window.checkBox: QCheckBox
            if self.window.checkBox.isChecked():
                self.window.doubleSpinBox_2.setValue(self.this_video_point_2)

    def init_pygameMixerTask(self):
        # 如果当前音频文件为空，或者被删除了不存在，返回False
        if self.this_video_path == '' or not os.path.exists(self.this_video_path):
            return False
        # 初始化待播放音频的文件路径
        self.pygameMixerTask.this_video_path = self.this_video_path
        """ 初始化待播放音频的开始时间 """
        self.window.horizontalScrollBar: QScrollBar
        # 如果此时是末尾，会自动切换为0
        if self.window.horizontalScrollBar.value() == self.window.horizontalScrollBar.maximum():
            self.window.horizontalScrollBar.setValue(0)
            self.this_video_point_1 = 0.0
            self.this_video_point_2 = 0.0
            self.window.label.setText('0.0')
        self.this_video_point_1 = self.this_video_point_2
        self.pygameMixerTask.this_start_point = self.this_video_point_1
        return True

    def pushButton_8_clicked(self):
        if self.this_video_path == '':
            QMessageBox.warning(self.window, '警告', '未选中任何音频')
            return
        # 将当前文件写入表格
        self.tableWidget_insertItems([self.this_video_path])

    def tableWidget_insertItems(self, path_list: str):
        column_index = self.current_column

        # 没有选中列，指定末尾追加
        if column_index == -1:
            column_index = self.window.tableWidget.columnCount()
        # 删除指定列再添加，相当于覆盖操作
        else:
            self.window.tableWidget.removeColumn(column_index)
        # 倒序，逐列追加
        for i in range(1, len(path_list)+1):
            self.window.tableWidget.insertColumn(column_index)
            self.window.tableWidget.setItem(0, column_index, QTableWidgetItem(path_list[-i]))

    def tableWidget_rightMenu(self, pos):
        menu = QMenu(self.window.tableWidget)
        menu.addAction('删除选中列')
        menu.addAction('前置添加列')
        menu.addAction('后置添加列')
        menu.addAction('清除所有列')
        menu.triggered.connect(self.tableWidget_rightMenu_triggered)
        menu.exec_(QCursor.pos())

    def tableWidget_rightMenu_triggered(self, act: QAction):
        self.window.tableWidget: QTableWidget
        if act.text() == '删除选中列':
            self.window.tableWidget.removeColumn(self.window.tableWidget.currentColumn())
        elif act.text() == '前置添加列':
            self.window.tableWidget.insertColumn(self.window.tableWidget.currentColumn())
        elif act.text() == '后置添加列':
            self.window.tableWidget.insertColumn(self.window.tableWidget.currentColumn()+1)
        elif act.text() == '清除所有列':
            self.window.tableWidget.setColumnCount(0)

    def tableWidget_itemClicked(self, item: QTableWidgetItem):
        self.current_column = item.column()
        self.window.label_7: QLabel
        self.window.label_7.setText('当前列索引:%s' % str(self.current_column))


